package hhf.services {

	import flash.events.StatusEvent;
	import flash.utils.ByteArray;
	import mx.utils.SHA256;
	import flash.utils.getQualifiedClassName;
	import mx.collections.ArrayCollection;
	import mx.resources.IResourceManager;
	import mx.resources.ResourceManager;
	import mx.core.FlexGlobals;
	import mx.rpc.AbstractOperation;
	import mx.rpc.AbstractService;
	import mx.rpc.AsyncToken;
	import mx.rpc.CallResponder;
	import mx.rpc.Fault;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.remoting.RemoteObject;
	import mx.utils.ObjectUtil;
	import hhf.net.NetworkMonitor;
	import mx.controls.Alert;
	import mx.events.PropertyChangeEvent;
	import flex.persistence.EntityManager;
	import flex.persistence.EntityManagerFactory;
	[ResourceBundle("ascreator")]

	[ExcludeClass]
	dynamic public class AbstractServiceResponder extends CallResponder	{


		public var em:EntityManager ;

		/**
		 * Constructor
		 */
		public function AbstractServiceResponder(destination:String, operationName:String) {
			if(getQualifiedClassName(this) == getQualifiedClassName(AbstractServiceResponder))
				throw new Error(getQualifiedClassName(AbstractServiceResponder)+" is Abstract");
			em = EntityManagerFactory.createEntityManager();
			this.operationName = operationName;
			this.resourceManager = ResourceManager.getInstance();
			this.service = new RemoteObject(destination);
			this.service.endpoint = resourceManager.getString("ascreator", "AMF");
			FlexGlobals.topLevelApplication.addEventListener(StatusEvent.STATUS, updateAvailability);
			// NetworkMonitor gere un singleton, un seul moniteur est réellement lancé.
			NetworkMonitor.start(resourceManager.getString("ascreator", "HOST"), resourceManager.getInt("ascreator", "PORT"), 2000);
		}

		protected function addNetworkHandlers():void {
			this.addEventListener(ResultEvent.RESULT, _resultNetworkHandler, false, 1000);
			this.addEventListener(FaultEvent.FAULT, faultNetworkHandler, false, 1000);
		}

		protected function removeNetworkHandlers():void {
			this.removeEventListener(ResultEvent.RESULT, _resultNetworkHandler);
			this.removeEventListener(FaultEvent.FAULT, faultNetworkHandler);
		}

		/**
		 * Si une erreur venant du serveur est survenue...
		 */
		private function faultNetworkHandler(event:FaultEvent):void {
			// l'appelle distant à échoué on vide le résultat
			(event.target as Object).datas = null;
			(event.target as AbstractServiceResponder).idle = true;
			// on propage les events typés
			//  
			//  et on laisse propager l'event
		}

		/**
		 * AbstractService
		 */
		private var service:AbstractService;
		private var operationName:String;
		private var resourceManager:IResourceManager;

		public var pollInterval:Number = 1000;

		/**
		 * Le cursor doit il afficher une occupation
		 */
		[Bindable]
		public function get showBusyCursor():Boolean {
			return service.showBusyCursor;
		}
		public function set showBusyCursor(showBusyCursor:Boolean):void {
			service.showBusyCursor = showBusyCursor;
		}

		/**
		 * Le service est il disponnible
		 */
		private var _available:Boolean = true;
		[Bindable]
		public function get available():Boolean {
			return _available && this.idle;
		}
		private function set available(available:Boolean):void {
			_available = available;
		}

		private var _idle:Boolean = true;
		public function get idle():Boolean {
			return _idle;
		}
		public function set idle(idle:Boolean):void {
			_idle = idle;
			var evt:PropertyChangeEvent = PropertyChangeEvent.createUpdateEvent(this, "available", this.available, !this.available);
			this.dispatchEvent(evt);
		}

		public function set auto(args:Array):void {
			trace("Autoload : "+getQualifiedClassName(this));
			trace("arguments : "+args);
			try {
				this.send.apply(this, args);
			} catch(e:Error) {
				trace(e);
			}
		}

		/**
		 * appel le service distant
		 */
		protected function callDatasFromNetwork(... args):void {
			if(NetworkMonitor.available) {
				this.idle = false;
				var op:AbstractOperation = service.getOperation(operationName);
				this.token = op.send.apply(op, args);
			}
		}

		/**
		 * Un résultat est survenue du réseau
		 */
		private function _resultNetworkHandler(event:ResultEvent):void {
			event.stopImmediatePropagation(); // on stope la propagation car setData va generer un nouvel evenement Result
			(event.target as AbstractServiceResponder).removeNetworkHandlers(); // on supprime les handlers interne
			resultNetworkHandler(event);
			(event.target as AbstractServiceResponder).idle = true;
		}

		/**
		 * Un résultat est survenue du réseau
		 */
		protected function resultNetworkHandler(event:ResultEvent):void {
			throw new Error(getQualifiedClassName(this)+" have to implement method resultNetworkHandler(event:ResultEvent):void {}.");
		}

		protected function updateAvailability(event:StatusEvent):void {
			throw new Error(getQualifiedClassName(this)+" have to implement method updateAvailability(event:StatusEvent):void {}.");
		}

		/**
		 * Retourne le hashcode d'un object
		 */
		public static function getHashCode(obj:Object):String {
			var hash:String = "";
			var b:ByteArray = new ByteArray();
			b.writeObject(obj);
			b.position = 0;
			hash = SHA256.computeDigest(b);
			return hash;
		}
	}
}